#define PHC_VERSION "3:0.0"
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/termios.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/semaphore.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/vt_kern.h>
#include <asm/uaccess.h>
#include <linux/parport.h>
#include <linux/hardirq.h>
#include <linux/parport_pc.h>
#include <linux/types.h>
#include <linux/delay.h>tty

#include "config.h"
#include "phc_lib.h"
#include "hd44780_inst_def.h"

MODULE_LICENSE("Dual MIT/GPL");
MODULE_DESCRIPTION( "LCD parallel port driver for HD44780 compatible controllers switched by a counter." );
MODULE_VERSION(PHC_VERSION);
/* used as port base and after port_init stores the value of counter */
static int base = DFLT_BASE;
module_param(base, int, 0 );
MODULE_PARM_DESC(base, "Parallel port i/o base address list default: DFLT_BASE.");

static unsigned int row_per_ctrl = DFLT_DISP_ROWS_PER_CTRL;
static unsigned int disp_cols = DFLT_DISP_COLS;
static unsigned int ctrls_num = DFLT_NUM_CONTROLLERS;
static unsigned int disp_rows = DFLT_NUM_CONTROLLERS * DFLT_DISP_ROWS_PER_CTRL;

static struct proc_dir_entry *phc_procfile;

static struct tty_driver *phc_tty_dri, *phc_ttykb_dri;
static struct tty_struct *current_tty = NULL;

unsigned int mcr = 0;
static unsigned int phc_kb_write_open = 0;

struct phcdev_t phcdev;
DEFINE_MUTEX(hd44780_mutex);

#include "hd44780_counter.c"
#include "hd44780_high.c"
#include "phc_console.c"
#include "phc_tty.c"
#include "phc_kb.c"
#include "phc_proc.c"

/* register port with given base and fills pd struct */
static int register_port(struct pardevice **pd, int base)
{
	struct parport *pp;
	pp = parport_find_base( base );
	if (!pp) {
		PERR( "no parport found at %#x.\n", base );
		return -ENODEV;
	}
	*pd = parport_register_device( pp, "phcmod", NULL, NULL, 
					NULL, PARPORT_DEV_EXCL, NULL);
	if (!*pd) {
		PERR("can't register device.\n");
		goto err_register_device;
	}
	if (parport_claim(*pd)) {
		PERR( "can't claim port.\n");
		goto err_claim;
	}
	return 0; /* succes */
	
err_claim:
	parport_unregister_device(*pd);
err_register_device:
	return -EBUSY;
}

static int __init phc_init(void)
{
	int i, ret = 0;
	PINFO("startup\n");
	disp_rows = ctrls_num * row_per_ctrl;
	PDEBUG("info %d %d %d %d\n", base, ctrls_num, disp_rows, disp_cols);
	
	/** initialize struct phcdev */
	phc_dev_init(&phcdev);
	/* alloc memory for screen state */
	phcdev.screen_size = disp_rows * disp_cols;
	phcdev.state = kcalloc(phcdev.screen_size, sizeof(*phcdev.state), GFP_KERNEL);
	if ( !phcdev.state ) {
		PERR("Cannot allocate memory\n");
		goto fail_phc_state_kcalloc;
	}
	memset(phcdev.state, charmap[' '], phcdev.screen_size*sizeof(*phcdev.state));
	/* alloc memory for lcds info */
	phcdev.lcd = kcalloc(ctrls_num, sizeof(*phcdev.lcd), GFP_KERNEL);
	if ( !phcdev.lcd ) {
		PERR("Cannot allocate memory\n");
		goto fail_phclcd_kcalloc;
	}
	/* register port */
	ret = register_port(&(phcdev.pd), base);
	if (ret) {
		PERR("Cannot register port at %#x.\n", base);
		goto fail_port_register;
	}
	PINFO( "port %#x registered.\n", base);
	/* init lcd informations */
	for(i = 0; i < ctrls_num; ++i)
		phc_lcd_init(&(phcdev.lcd[i]));
	
	
	/** initialize all controllers and write cgram */
	init_displays();
	
	
	/** create proc read entries */
	ret = -ENOMEM;
	phc_procfile = create_proc_entry("phc", 0444, NULL);
	if(phc_procfile == NULL) {
		PERR("Cannot create /proc/phc/phc entry.\n");
		goto fail_proc_phc;
	}
	phc_procfile->data = NULL;
	phc_procfile->read_proc = phc_proc_read;
	phc_procfile->write_proc = NULL;
	
	/** tty kb driver */
	ret = -ENOMEM;
	phc_ttykb_dri = alloc_tty_driver(1);
	if (!phc_ttykb_dri) {	
		PERR("cant alloc memory tty_driver");
		goto fail_alloc_ttyKB_driver;
	
	}
	phc_ttykb_dri->owner = THIS_MODULE;
	phc_ttykb_dri->driver_name = "phc_tty_kb";
	phc_ttykb_dri->name = "ttyK";
	phc_ttykb_dri->major = PHC_TTY_MAJOR;
	phc_ttykb_dri->minor_start = 1;
	phc_ttykb_dri->type = TTY_DRIVER_TYPE_CONSOLE;
	//phc_ttykb_dri->subtype = SYSTEM_TYPE_CONSOLE;
	phc_ttykb_dri->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS;
	phc_ttykb_dri->num = 1;
	phc_ttykb_dri->init_termios = tty_std_termios;
	tty_set_operations(phc_ttykb_dri, &phc_ttykb_ops);
	ret = tty_register_driver(phc_ttykb_dri);
	if ( ret ) {
		printk("Cannot register tty device \n");
		goto fail_ttykb_register_driver;
	}
	
	/** main tty driver */
	ret = -ENOMEM;
	phc_tty_dri = alloc_tty_driver(PHC_TTY_MINORS);
	if (!phc_tty_dri) {	
		PERR("cant alloc memory tty_driver");
		goto fail_alloc_tty_driver;
	
	}
	phc_tty_dri->owner = THIS_MODULE;
	phc_tty_dri->driver_name = "phc_tty";
	phc_tty_dri->name = "ttyP";
	phc_tty_dri->major = PHC_TTY_MAJOR;
	phc_tty_dri->minor_start = 0;
	phc_tty_dri->type = TTY_DRIVER_TYPE_SYSTEM;
	phc_tty_dri->subtype = SYSTEM_TYPE_TTY;
	phc_tty_dri->flags = TTY_DRIVER_REAL_RAW;
	phc_tty_dri->num = PHC_TTY_MINORS;
	phc_tty_dri->init_termios = tty_std_termios;
	tty_set_operations(phc_tty_dri, &phc_tty_ops);
	ret = tty_register_driver(phc_tty_dri);
	if ( ret ) {
		printk("Cannot register tty device \n");
		goto fail_tty_register_driver;
	}
	
	/* retister char device kb for tty */
	/*ret = (long int)register_chrdev(PHC_KB_MAJOR, "ttyphckb", &phc_kb_fops);
	if ( ret ) {
		printk("Cannot register char device \n");
		goto fail_register_chrdev_kb;
	}*/
	
	
	//PINFO( "init OK, irq on, controlers %d, rows: %d, columns: %d\n", ctrls_num * ports_number, disp_rows, disp_cols );
	return 0; /* succes */

//fail_register_chrdev_kb:
	//unregister_chrdev ( PHC_KB_MAJOR, "ttyphckb" );
fail_tty_register_driver:
	tty_unregister_driver(phc_tty_dri);
fail_alloc_tty_driver:
fail_ttykb_register_driver:
	tty_unregister_driver(phc_ttykb_dri);
fail_alloc_ttyKB_driver:
        //remove_proc_entry("phc_all", phc_procdir);
//fail_proc_phc_all:
        //remove_proc_entry("phc_long", phc_procdir);
//fail_proc_phc_long:
        remove_proc_entry("phc", NULL);
fail_proc_phc:
        //remove_proc_entry("phc", NULL);
//fail_proc_mkdir:
fail_phclcd_kcalloc:
	kfree(phcdev.lcd);
fail_phc_state_kcalloc:
	kfree(phcdev.state);
	parport_release(phcdev.pd);
	parport_unregister_device(phcdev.pd);
fail_port_register:
	return ret;
}

static void __exit phc_exit(void)
{
	int i;
	printk("rmmod test1\n");
	unregister_chrdev ( PHC_KB_MAJOR, "ttyphckb" );
	for (i=0; i< PHC_TTY_MINORS; ++i) {
		tty_unregister_device(phc_tty_dri, i);
	}
	tty_unregister_device(phc_ttykb_dri, 0);
	tty_unregister_driver(phc_tty_dri);
	tty_unregister_driver(phc_ttykb_dri);
	tty_driver_kref_put(phc_tty_dri);
        remove_proc_entry("phc", NULL);
	kfree(phcdev.state);
	kfree(phcdev.lcd);
	parport_release(phcdev.pd);
	parport_unregister_device(phcdev.pd);
}

module_init(phc_init);
module_exit(phc_exit);
